package com.tsd.interceptor;

import com.alibaba.fastjson.JSON;
import com.tsd.core.constants.Constants;
import com.tsd.core.constants.JwtConstants;
import com.tsd.core.datasource.DataSourceContextHolder;
import com.tsd.core.utils.ExceptionUtil;
import com.tsd.core.utils.HlpUtils;
import com.tsd.job.QuartzJob;
import com.tsd.system.dao.SysLoginInfoMapper;
import com.tsd.system.entity.SysLogInfoExt;
import com.tsd.system.entity.SysLoginInfoExt;
import com.tsd.system.service.SysLogInfoService;
import io.jsonwebtoken.Claims;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * 访问日志 拦截器
 * @author Luoyc
 */
@Component
public class LogInterceptor implements HandlerInterceptor {

    public static final Logger LOGGER = LoggerFactory.getLogger(LogInterceptor.class);

    @Resource
    private SysLoginInfoMapper sysLoginInfoMapper;
    @Resource
    private SysLogInfoService sysLogInfoService;
    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Resource
    private QuartzJob quartzJob;
    long startTime;

    /**
     * preHandle方法是进行处理器拦截用的，顾名思义，该方法将在Controller处理之前进行调用，
     * SpringMVC中的Interceptor拦截器是链式的，可以同时存在多个Interceptor，
     * 然后SpringMVC会根据声明的前后顺序一个接一个的执行，
     * 而且所有的Interceptor中的preHandle方法都会在Controller方法调用之前调用。
     * SpringMVC的这种Interceptor链式结构也是可以进行中断的，
     * 这种中断方式是令preHandle的返回值为false，当preHandle的返回值为false的时候整个请求就结束了。
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        startTime = System.currentTimeMillis();
        return true;
    }

    /**
     * 这个方法只会在当前这个Interceptor的preHandle方法返回值为true的时候才会执行。
     * postHandle是进行处理器拦截用的，它的执行时间是在处理器进行处理之后，也就是在Controller的方法调用之后执行，
     * 但是它会在DispatcherServlet进行视图的渲染之前执行，也就是说在这个方法中你可以对ModelAndView进行操作。
     * 这个方法的链式结构跟正常访问的方向是相反的，也就是说先声明的Interceptor拦截器该方法反而会后调用，
     * 这跟Struts2里面的拦截器的执行过程有点像，
     * 只是Struts2里面的intercept方法中要手动的调用ActionInvocation的invoke方法，
     * Struts2中调用ActionInvocation的invoke方法就是调用下一个Interceptor
     * 或者是调用action，然后要在Interceptor之前调用的内容都写在调用invoke之前，
     * 要在Interceptor之后调用的内容都写在调用invoke方法之后。
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
        writeLog(request, response, handler);
    }

    @SuppressWarnings("unchecked")
    private void writeLog(HttpServletRequest request, HttpServletResponse response, Object handler) {
        try {
            final SysLogInfoExt logInfoExt = new SysLogInfoExt();
            //X-real-ip是通过Nginx代理跳转过来时所设置的实际ip
            String remoteIp = request.getHeader("X-real-ip");
            if (HlpUtils.isEmpty(remoteIp)) {
                remoteIp = request.getRemoteAddr();
            }
            String accountSetSid = DataSourceContextHolder.getDataSourceType();
            Claims claims = (Claims) request.getAttribute(Constants.REQ_ATTRIBUTE_NAME_CLAIMS);
            Map<String, Object> params = (Map<String, Object>) request.getAttribute(Constants.REQ_ATTRIBUTE_NAME_PARAMS);
            String deviceType = request.getHeader(Constants.REQ_HEADER_NAME_DEVICE_TYPE);
            if (HlpUtils.isEmpty(deviceType)) {
                deviceType = HlpUtils.getString(params, Constants.REQ_HEADER_NAME_DEVICE_TYPE);
            }
            logInfoExt.setDevice_type(deviceType);
            logInfoExt.setDuration_ms((int) (System.currentTimeMillis() - startTime));
            logInfoExt.setLog_time(new Date(startTime));
            if (handler instanceof HandlerMethod) {
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                ApiOperation annotation = handlerMethod.getMethod().getAnnotation(ApiOperation.class);
                if (annotation != null) {
                    String opName = annotation.value();
                    logInfoExt.setOperation(opName);
                }
            }
            logInfoExt.setUri(request.getRequestURI());
            logInfoExt.setParams_data(JSON.toJSONString(params));
            //此处不适宜读取httpresult的code，需要跳转action返回的机制
            logInfoExt.setResult_code(response.getStatus() + "");
            logInfoExt.setUser_agent(request.getHeader("User-Agent"));
            logInfoExt.setReferer(request.getHeader("Referer"));
            if (claims != null) {
                logInfoExt.setUser_sid((String) claims.get(JwtConstants.CLAIM_NAME_SID));
                logInfoExt.setUser_name((String) claims.get(JwtConstants.CLAIM_NAME_USER_NAME));
            }
            logInfoExt.setUser_ip(remoteIp);
            String la = request.getLocalAddr();
            if ("127.0.0.1".equals(la) || "localhost".equalsIgnoreCase(la)) {
                la = request.getServerName();
            }
            logInfoExt.setServer_ip(la);
            logInfoExt.setServer_port(request.getServerPort());
            if (logInfoExt.getUri() != null && logInfoExt.getUri().length() > 255) {
                logInfoExt.setUri(logInfoExt.getUri().substring(1, 255));
            }
            if (logInfoExt.getUser_agent() != null && logInfoExt.getUser_agent().length() > 255) {
                logInfoExt.setUser_agent(logInfoExt.getUser_agent().substring(1, 255));
            }
            if (logInfoExt.getReferer() != null && logInfoExt.getReferer().length() > 255) {
                logInfoExt.setReferer(logInfoExt.getReferer().substring(1, 255));
            }
            logInfoExt.setHeader_data(getHeaderJson(request));
            threadPoolTaskExecutor.execute(() -> {
                try {
                    //输出到账套日志库
                    String log = accountSetSid + Constants.DATABASE_SUFFIX_FOR_LOG;
                    DataSourceContextHolder.setDataSourceType(log);
                    sysLogInfoService.saveSysLogInfo(logInfoExt);
                } catch (Exception e) {
                    LOGGER.error(ExceptionUtil.getStackTraceFromException(e));
                    //异常，可能是表不存在，则创建日志库
                    quartzJob.createTableLogInfo4AccountSet(accountSetSid);
                }
            });
        } catch (Exception e) {
            LOGGER.error(ExceptionUtil.getStackTraceFromException(e));
        }
    }

    private String getHeaderJson(HttpServletRequest request) {
        Map<String, String> map = new HashMap<>();
        Enumeration<String> names = request.getHeaderNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            map.put(name, request.getHeader(name));
        }
        return JSON.toJSONString(map);
    }


    /**
     * 该方法也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行。
     * 该方法将在整个请求完成之后，也就是DispatcherServlet渲染了视图执行，
     * 这个方法的主要作用是用于清理资源的
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    }

}
